#include <sys/socket.h>
#include <netinet/in.h>

#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libubox/ustream.h>
#include <libubox/uloop.h>
#include <libubox/usock.h>

static struct uloop_fd server;
static const char *port = "10000";

enum client_state {
	IDLE = 0,
	REQUEST_DOORID,
	SEND_DOORID,
	REQUEST_OPEN_DOOR,
	SEND_OPEN_DOOR,
};

enum cmd_id {
	CMD_REQUEST_DOORID = 'A',
	CMD_RESPONSE_DOORID = 'B',
	CMD_REQUEST_OPENDOOR = 'C',
	CMD_RESPONSE_OPENDOOR = 'D',
	CMD_RANDOMSEED = 'E',
};

struct request_cmd {
	enum cmd_id cmd;
};

#define DEFAULT_TIMEOUT   1000 /* 1s */
struct client *next_client = NULL;

struct client {
	struct sockaddr_in sin;

	struct ustream_fd s;
	int ctr;
	enum client_state state;

	struct uloop_timeout timeout;
};

static char *get_doorid()
{
	//TODO: get door id 
	return "01234567890";
}

#define MAX_LEN 1024
#define MAX_TOKENLEN 512

static int handle_request_doorid(struct client *cl)
{
	const char *doorid = get_doorid();
	int len = strlen(doorid) + 1;	// add '\0'
	char buf[MAX_LEN] = {0};

	uloop_timeout_cancel(&cl->timeout);

	buf[0] = CMD_RESPONSE_DOORID;
	buf[1] = len;
	strcpy(&buf[2], doorid);

	ustream_write(&cl->s.stream, buf, len + 2, false);

	cl->state = SEND_DOORID;

	uloop_timeout_set(&cl->timeout, DEFAULT_TIMEOUT);

	return 0;
}

// TODO: decrypt token
static int decrypt(const char *inbuf, char *outbuf, int len)
{
#ifdef DECRYPT
	decrypt_tobeimplemented(inbuf, outbuf, len);
#else
	memcpy(outbuf, inbuf, len);
#endif
	return 0;
}


static int parse_decrypted_token(const char *token, char *mac, char *phone, char *imei)
{
	// use strncpy instead
	strcpy(mac, "00:11:22:33:44:55");
	strcpy(phone, "1381381111");
	strcpy(imei, "012345678901234567890");

	return 0;
}

static int door_request_valid(char *decrypted_mac, char *decrypted_phone, char *decrypted_imei,
	char *plain_mac, char *plain_phone, char *plain_imei)
{
	// TODO: compare 
	return 1;
}

static int handle_open_door(struct client *cl, char *data, int len)
{
	char *token = (char *)malloc(len + 4);
	char mac[32];
	char phone[32];
	char imei[32];
	char resp[4] = {CMD_RESPONSE_OPENDOOR, 1, 0};
	char succ = 0;

	uloop_timeout_cancel(&cl->timeout);

	if (!token)
		goto out;

	if (decrypt(data, token, len)) { // failed
		goto out;
	}

	if (parse_decrypted_token(token, mac, phone, imei))
		goto out;

	if (door_request_valid(mac, phone, imei, NULL, NULL, NULL) == 0)
		goto out;

	succ = 1;

	resp[2] = succ;

out:
	ustream_write(&cl->s.stream, resp, 3, false);
}


static void handle_client_request(struct client *cl, char * data, int len)
{
	enum cmd_id cmd = data[0];

	switch (cmd) {
	case CMD_REQUEST_DOORID:
		fprintf(stderr, "received request doorid\n");
		handle_request_doorid(cl);
		break;

	case CMD_REQUEST_OPENDOOR:
		fprintf(stderr, "received opendoor request\n");
		handle_open_door(cl, data, len);
		
		break;
	default:
		// pass
		break;
	}
}

static void client_read_cb(struct ustream *s, int bytes)
{
	struct client *cl = container_of(s, struct client, s.stream);
	//struct ustream_buf *buf = s->r.head;
	char *data;
	int len;

	do {
		data = ustream_get_read_buf(s, &len);
		if (!data)
			break;

		handle_client_request(cl, data, len);

		ustream_consume(s, len);
	} while(1);

	if (s->w.data_bytes > 256 && !ustream_read_blocked(s)) {
		fprintf(stderr, "Block read, bytes: %d\n", s->w.data_bytes);
		ustream_set_read_blocked(s, true);
	}
}

static void client_close(struct ustream *s)
{
	struct client *cl = container_of(s, struct client, s.stream);

	fprintf(stderr, "Connection closed\n");
	ustream_free(s);
	close(cl->s.fd.fd);
	free(cl);
}

static void client_notify_write(struct ustream *s, int bytes)
{
	fprintf(stderr, "Wrote %d bytes, pending: %d\n", bytes, s->w.data_bytes);

	if (s->w.data_bytes < 128 && ustream_read_blocked(s)) {
		fprintf(stderr, "Unblock read\n");
		ustream_set_read_blocked(s, false);
	}
}

static void client_notify_state(struct ustream *s)
{
	struct client *cl = container_of(s, struct client, s.stream);

	if (!s->eof)
		return;

	fprintf(stderr, "eof!, pending: %d, total: %d\n", s->w.data_bytes, cl->ctr);
	if (!s->w.data_bytes)
		return client_close(s);

}

static void client_timeout(struct uloop_timeout *t)
{
	struct client *cl = container_of(t, struct client, timeout);

	cl->state = IDLE;

	fprintf(stderr, "client timeout, force close\n");
	client_close(&cl->s.stream);
}

static void server_cb(struct uloop_fd *fd, unsigned int events)
{
	struct client *cl;
	unsigned int sl = sizeof(struct sockaddr_in);
	int sfd;

	if (!next_client)
		next_client = calloc(1, sizeof(*next_client));

	cl = next_client;
	sfd = accept(server.fd, (struct sockaddr *) &cl->sin, &sl);
	if (sfd < 0) {
		fprintf(stderr, "Accept failed\n");
		return;
	}

	//cl->s.stream.string_data = true;
	cl->s.stream.notify_read = client_read_cb;
	cl->s.stream.notify_state = client_notify_state;
	cl->s.stream.notify_write = client_notify_write;
	ustream_fd_init(&cl->s, sfd);
	next_client = NULL;
	fprintf(stderr, "New connection\n");

	//start timer
	cl->timeout.cb = client_timeout;

	uloop_timeout_set(&cl->timeout, DEFAULT_TIMEOUT);
}

static int run_server(void)
{

	server.cb = server_cb;
	server.fd = usock(USOCK_TCP | USOCK_SERVER | USOCK_IPV4ONLY | USOCK_NUMERIC, "0.0.0.0", port);
	if (server.fd < 0) {
		perror("usock");
		return 1;
	}

	uloop_init();
	uloop_fd_add(&server, ULOOP_READ);
	uloop_run();

	return 0;
}

static int usage(const char *name)
{
	fprintf(stderr, "Usage: %s -p <port>\n", name);
	return 1;
}

int main(int argc, char **argv)
{
	int ch;

	while ((ch = getopt(argc, argv, "p:")) != -1) {
		switch(ch) {
		case 'p':
			port = optarg;
			break;
		default:
			return usage(argv[0]);
		}
	}

	return run_server();
}
